﻿using Heng.Elements.Core.Model.Infrastructure.Entities;
using Heng.Elements.Core.Model.Infrastructure.Messages;
using Heng.Elements.Core.Model.Infrastructure.Policy;
using Heng.Elements.Core.Model.Infrastructure.Repositories;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Heng.Elements.Core.Model.Infrastructure.Services
{
    /// <summary>
    /// Base implementation for entity service.
    /// </summary>
    /// <typeparam name="TEntity"> Entity type. </typeparam>
    public abstract class EntityServiceBase<TEntity> : IEntityService<TEntity>
        where TEntity : class, IEntity
    {
        #region Member Variables
        // user repository context passed externaly which needs to be used by all subsequent calls
        EntityRepositoryContext _customRepoCtx = null;
        #endregion

        /// <summary>
        /// Ctor.
        /// </summary>
        public EntityServiceBase()
        {
        }

        /// <summary>
        /// Ctor.
        /// </summary>
        /// <param name="customRepoCtx"> Custom repository context to be used by all subsequent operations. </param>
        public EntityServiceBase(EntityRepositoryContext customRepoCtx)
        {
            _customRepoCtx = customRepoCtx;
        }

        /// <summary>
        /// Execute action in context.
        /// </summary>
        /// <param name="func"> Function to execute. </param>
        protected ServiceResponse<TRes> ExecInContext<TRes>(Func<EntityRepositoryContext, ServiceResponse<TRes>, TRes> func)
        {
            var response = PolicyManager.Exec<TRes>(resInner =>
                {
                    TRes data = default(TRes);

                    EntityRepositoryContext repoCtx = null;

                    if (_customRepoCtx == null)
                    {
                        // if no custom repository context was set then execute using new context
                        repoCtx = new EntityRepositoryContext();
                    }
                    else
                    {
                        // if a custom repository context was set then execute using custom context
                        repoCtx = _customRepoCtx;
                    }

                    try
                    {
                        // execute action in context
                        data = func(repoCtx, resInner);
                    }
                    finally
                    {
                        if (_customRepoCtx == null)
                        {
                            // no custom context therefore the context is local and should be disposed
                            repoCtx.Dispose();
                        }
                        else
                        {
                            // custom context therefore the context is custom and should not be disposed
                        }
                    }

                    return data;
                });

            return response;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="action"></param>
        /// <returns></returns>
        protected ServiceResponse ExecInContext(Action<EntityRepositoryContext, ServiceResponse> action)
        {
            ServiceResponse response = new ServiceResponse();

            ServiceResponse<object> resAux = ExecInContext<object>((ctx, res) =>
                {
                    action(ctx, response);
                    return null;
                });

            response.Merge(resAux);

            return response;
        }

        public ServiceResponse<TEntity> GetById(long id)
        {
            var response = ExecInContext<TEntity>((ctx, res) =>
                {
                    var repo = ctx.GetEntityRepository<TEntity>();
                    var ent = repo.GetById(id);
                    return ent;
                });

            return response;
        }

        public ServiceResponse<IEnumerable<TEntity>> GetAll()
        {
            var response = ExecInContext<IEnumerable<TEntity>>((ctx, res) =>
            {
                var repo = ctx.GetEntityRepository<TEntity>();
                var entities = repo.GetAll();
                return entities;
            });

            return response;
        }

        public ServiceResponse Add(TEntity entity)
        {
            var response = ExecInContext((ctx, res) =>
            {
                var repo = ctx.GetEntityRepository<TEntity>();
                repo.Add(entity);
            });

            return response;
        }

        public ServiceResponse Save(TEntity entity)
        {
            var response = ExecInContext((ctx, res) =>
            {
                var repo = ctx.GetEntityRepository<TEntity>();
                repo.Save(entity);
            });

            return response;
        }

        public ServiceResponse Delete(TEntity entity)
        {
            var response = ExecInContext((ctx, res) =>
            {
                var repo = ctx.GetEntityRepository<TEntity>();
                repo.Delete(entity);
            });

            return response;
        }

        public ServiceResponse Delete(long id)
        {
            var response = ExecInContext((ctx, res) =>
            {
                var repo = ctx.GetEntityRepository<TEntity>();
                repo.Delete(id);
            });

            return response;
        }
    }
}
